//  Controller.m
//
//  Created by Ryan on Thu Sep 25 2003.
//  Chimoosoft (c) 2003 Chimoosoft. All rights reserved.
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.


#import "Controller.h"
#import "PreferenceController.h"
#import "TimerApplication.h"
#import "CMSSpeech.h"
#import "StopWatch.h"
#import "CMSTimeFormatter.h"
#import "MiniWindowController.h"
#import <CMSDonations/CMSDonations.h>
#import "CMSCommon.h"
#import "Notifier.h"

@interface Controller ()

- (void)restoreMainWindowFontAndColor;
- (void)restoreMultiplierWindowFontAndColor;
- (void)userDefaultsChanged:(NSNotification *)aNotification;
- (void)updateFromDirectionChange;
- (void)updateFromStartStop;
- (void)updateTextReadouts:(StopWatch*)theWatch;
- (void)resizeWindow:(id)window withNewFont:(NSFont*)font;
- (void)setupMenus;

@end


@implementation Controller

- (id)init {
	
	if (self = [super init]) {

		notifier = [[Notifier alloc] init];
		
		internalFormatter = [[CMSTimeFormatter alloc] init];
		secTf = [[CMSTimeFormatter alloc] init];
		[secTf setUseSecondsOnly:YES];
		[secTf setShowSecondFractions:NO];

		mainToolbar = nil;
		multiplierToolbar = nil;
		statusBar = nil;
		statusItem = nil;
		SRActive = NO;
		hasWarned = NO;
		musicWasPlaying = NO;
		alreadyStoppedMusic = NO;
		speechRecognizer = nil;
		startStopText = @"Start";
		internalFontUpdate = NO;
		
		inForeGround = YES;
		enableMini = enableMulti = enableMain = enableMenu = NO;
		multiplierFactor = 1.0;
		
		speech = [[CMSSpeech sharedInstance] retain];
	}
	
	return self;
}

- (void)dealloc {
	
	[notifier release];
	[sharedTimeFormatter release];
	[internalFormatter release];
	[secTf release];
	[speech release];	
	[defaults release];
	[stopWatch release];
	
	[secondTickSound release];
	[minuteTickSound release];
	[hourTickSound release];
	
	[mainToolbar release];
	[multiplierToolbar release];
	
	[preferenceController release];
	
	// Remove this object as an observer in the NotificationCenter.
	[[NSNotificationCenter defaultCenter] removeObserver: self];
	
	[endSound release];
	
	if (SRActive) {
		// Stop speech recognition.
		[speechRecognizer stopListening];
	}
	
	[speechRecognizer release];
	
	[statusBar release];
	[statusItem release];
	
	[super dealloc];
}


- (void)awakeFromNib {	
	if (alreadyAwoke) return;		// don't want to run this stuff more than once.
	alreadyAwoke = YES;
	
	#if DEBUG
	NSLog(@"Debug Mode Enabled.");
	#endif
	
	[CMSCommon quitIfNotTiger];
	
	
	preferenceController = [[PreferenceController alloc] init];
	[preferenceController setMainController:self];
	[preferenceController setupDefaults];
	
	defaults = [[NSUserDefaults standardUserDefaults] retain];

	[self updateSounds];
	
	// Create the master stopWatch instance.
	stopWatch = [[StopWatch alloc] init];
	[stopWatch setDelegate:self];
	[stopWatch setRefreshRate:[self currentRefreshRate]];
	if ([stopWatch isInCountDownMode] != [defaults boolForKey:@"countDownAtLastLaunch"]) {
		[self toggleDirection:self];
	}
		
	NSNotificationCenter *nc;
	nc = [NSNotificationCenter defaultCenter];
					
	NSNotificationCenter * nCenter = [NSNotificationCenter defaultCenter];
	
	// Set this class up as an observer for the notification
	// which occurs when the space bar is pressed
	[nCenter  addObserver: self
			selector: @selector(spacePressed:)
				name: @"MOOSpacePressed"
			  object: nil];	
	
	[nCenter addObserver:self
				selector:@selector(userDefaultsChanged:)
					name:NSUserDefaultsDidChangeNotification
				  object:nil];
	

	if (![defaults boolForKey:@"dockIconIsCurrentlyHidden"]) {	
		[self restoreMainWindowFontAndColor];
		[self restoreMultiplierWindowFontAndColor];
		
		// setup and add the toolbars
		mainToolbar = [[NSToolbar alloc] initWithIdentifier:@"mainToolbar"];
		[mainToolbar setDelegate:self];
		[mainToolbar setDisplayMode: NSToolbarDisplayModeIconOnly];
		[mainToolbar setAllowsUserCustomization:YES];
		[mainToolbar setAutosavesConfiguration:YES];	
		[mainWindow setToolbar:mainToolbar];
		
		multiplierToolbar = [[NSToolbar alloc] initWithIdentifier:@"multiplierToolbar"];
		[multiplierToolbar setDelegate:self];
		[multiplierToolbar setDisplayMode: NSToolbarDisplayModeIconOnly];
		[multiplierToolbar setAllowsUserCustomization:YES];
		[multiplierToolbar setAutosavesConfiguration:YES];
		[multiplierWindow setToolbar:multiplierToolbar];
	}
	
	if ([defaults boolForKey:@"speakSeconds"] ||
		[defaults boolForKey:@"speakAlertOnExpire"] || 
		[defaults boolForKey:@"speakTimeOnInterval"]) {
		[speech startCooking];  // just to get things cooking
	}
	
	[self userDefaultsChanged:nil];

	[self updateFromDirectionChange];
	[self updateFromStartStop];
	[self updateTextReadouts:stopWatch];
}

- (void)restoreMainWindowFontAndColor {
	internalFontUpdate = YES;
	
	NSData * data = [defaults objectForKey:@"mainWindowFont"];
	if (data) [self resizeWindow:mainWindow withNewFont:[NSUnarchiver unarchiveObjectWithData:data]];
	
	data = [defaults objectForKey:@"mainWindowTextColor"];
	if (data) [readout setTextColor:[NSUnarchiver unarchiveObjectWithData:data]];
	
	internalFontUpdate = NO;
}

- (void)restoreMultiplierWindowFontAndColor {
	internalFontUpdate = YES;
	
	NSData * data = [defaults objectForKey:@"multiplierWindowFont"];
	if (data) [self resizeWindow:multiplierWindow withNewFont:[NSUnarchiver unarchiveObjectWithData:data]];
	
	data = [defaults objectForKey:@"multiplierWindowTextColor"];
	if (data) [multiplierField setTextColor:[NSUnarchiver unarchiveObjectWithData:data]];
	
	internalFontUpdate = NO;
}


- (void)initializeSpeechRecognition {
	if (!SRActive) return;
	
	if (nil == speechRecognizer) speechRecognizer = [[NSSpeechRecognizer alloc] init];
	
	NSMutableArray * commands = [NSMutableArray arrayWithCapacity:10];
	[commands addObjectsFromArray: [NSArray arrayWithObjects: @"Open preferences", @"Disable speech recognition", @"Speak time", @"Current value", @"Log time", @"Reset timer", nil]];

	if ([stopWatch isRunning]) [commands addObject:@"Stop timer"];
	else [commands addObject:@"Start timer"];
	
	if ([stopWatch isInCountDownMode]) [commands addObject:@"Switch to count up mode"];
	else [commands addObject:@"Switch to count down mode"];
	
	[speechRecognizer setCommands:commands];
	[speechRecognizer setListensInForegroundOnly:NO];
	[speechRecognizer setDelegate:self];
	
	[speechRecognizer startListening];
}

- (void)speechRecognizer:(NSSpeechRecognizer *)sender didRecognizeCommand:(id)aCmd {
	NSString * command = (NSString*)aCmd;
	
	if ([command isEqualToString:@"Disable speech recognition"]) {
		[speechRecognizer stopListening];
		[defaults setBool:NO forKey:@"enableSpeechRecognition"];
		[self toggleSpeechRecognition:self];
		return;
	}
	
	if ([command isEqualToString:@"Open preferences"]) {
		[self showPreferencePanel:self];
	}
	
	if ([command isEqualToString:@"Current value"] ||
		[command isEqualToString:@"Speak time"]) {

		[self performSelector:@selector(elapsed:)
				   withObject:self
				   afterDelay:1.0];
		return;
	}

	if ([command isEqualToString:@"Log time"]) {
		[self logTime:self];
		
		return;
	}

	if ([command isEqualToString:@"Start timer"]) {
		if ([stopWatch isRunning]) return;
		[self startStop: self];
		
		return;
	}

	if ([command isEqualToString:@"Stop timer"]) {
		if (![stopWatch isRunning]) return;
		[self startStop: self];
				
		return;
	}

	if ([command isEqualToString:@"Reset timer"]) {
		[self reset: self];

		return;
	}
	
	if ([command isEqualToString:@"Switch to count up mode"]) {
		[self setDirectionUp:self];
		[self initializeSpeechRecognition];
		return;
	}

	if ([command isEqualToString:@"Switch to count down mode"]) {
		[self setDirectionDown:self];
		[self initializeSpeechRecognition];
		return;
	}
	
	
}



- (BOOL)validateMenuItem:(NSMenuItem*)anItem {
	int tag = [anItem tag];
	
	switch (tag) {
		case 1006:
			return YES;
			break;
		case 2000:
			return ([[[logTextView textStorage] string] length] > 0);
			break;
		default:
			break;
	}
	
	return YES;
}


- (void)updateStopWatchCountDownTime {
	secDown = (double) [defaults integerForKey:@"hMenuSetting"]*3600.0
	+ [defaults integerForKey:@"mMenuSetting"]*60.0
	+ [defaults integerForKey:@"sMenuSetting"];
	
	[stopWatch setUserCountDownSeconds: secDown];
}

- (IBAction)countDownTimeAdjusted:(id)sender {
	[self updateStopWatchCountDownTime];
	
	if (![stopWatch isRunning]) {
		if (![stopWatch isInCountDownMode]) [self toggleDirection:self];
		[self reset:self];
	}
}

- (IBAction)showSetCountdownWindow:(id)sender {
	[setCountDownWindow center];
	[setCountDownWindow makeKeyAndOrderFront:sender];
}

- (IBAction)clearLog:(id)sender {
	NSRange range;
	range.location = 0;
	range.length = [[logTextView textStorage] length];
	
	[logTextView setSelectedRange:range];
	[logTextView setEditable:YES];
	[logTextView delete: nil];
	[logTextView setEditable:NO];
}


- (IBAction)logTime:(id)sender {

	[internalFormatter setUseHMSFormat:YES];
	[internalFormatter setShowSecondFractions:YES];	
	[logDrawer open];
	
	NSNumber * time = [stopWatch elapsedTimeInSecondsAsNumber];
	NSDate * date = [NSDate date];
	double mult = multiplierFactor * [time doubleValue];
	
	NSString * string = [NSString stringWithFormat:@"%@\t%@\t%.2f\n", [date description], [internalFormatter stringForObjectValue:time], mult];
	[[[logTextView textStorage] mutableString] appendString:string];

	// scroll to bottom
	NSPoint newScrollOrigin;
	
    if ([[logScrollView documentView] isFlipped]) {
        newScrollOrigin = NSMakePoint(0.0, NSMaxY([[logScrollView documentView] frame]) -
									  NSHeight([[logScrollView contentView] bounds]));
    } else {
        newScrollOrigin = NSMakePoint(0.0,0.0);
    }
	
    [[logScrollView documentView] scrollPoint:newScrollOrigin];
	
}


- (IBAction)saveLog:(id)sender {
	NSString * string = [[logTextView textStorage] string];
	if ([string length] <= 0) return;
	
	NSSavePanel *panel = [NSSavePanel savePanel];
	[panel setRequiredFileType:@"txt"];
	[panel beginSheetForDirectory:[@"~/Desktop" stringByExpandingTildeInPath]
							 file:nil 
				   modalForWindow:mainWindow
					modalDelegate:self
				   didEndSelector:@selector(savePanelDidEnd:returnCode:contextInfo:)
					  contextInfo:nil];
}


// this is called when the user closes the save sheet
- (void)savePanelDidEnd:(NSSavePanel *)sheet returnCode:(int)returnCode  contextInfo:(void*)contextInfo {
	
	if (returnCode != NSOKButton) return;
	
	NSData *data;
	
	NSString * string = [[logTextView textStorage] string];
	data = [string dataUsingEncoding:NSUTF8StringEncoding];		
	
	[data writeToFile: [sheet filename] atomically: YES];	
}



- (IBAction)closeThisWindow:(id)sender {
	[[sender window] close];
}



- (IBAction)menuChosen:(id)sender {
}

- (IBAction)elapsed:(id)sender {
	[internalFormatter setUseSpeakableFormat:YES];
	[internalFormatter setShowSecondFractions:[defaults boolForKey:@"showSecondFractions"]];
	
	[speech stopSpeakingAndSpeakString:[internalFormatter stringForObjectValue:[stopWatch elapsedTimeInSecondsAsNumber]]];
}

- (void)spacePressed:(id)object {
	// space bar was pressed
	
	if (!inForeGround) return;
	
	id keyWindow = [[TimerApplication sharedApplication] keyWindow];
	
	if (nil == keyWindow) {
		// app is in background, or no window selected
		[self startStop:self];
		return;
	}
	
	NSSet * windows = [NSSet setWithObjects: mainWindow, miniWindow, multiplierWindow, nil];
	if ([windows containsObject: keyWindow]) {
		[self startStop:self];
	}	
}



// Show the about panel to the user.
- (IBAction)showAboutPanel:(id)sender {
	if (aboutPanel == nil) {
		if (![NSBundle loadNibNamed:@"AboutPanel.nib" owner:self] ) {
			NSLog(@"Load of AboutPanel.nib failed");
			return;
		}
	}

	// Give them a spoken welcome.	
	[speech stopSpeakingAndSpeakString:[NSString stringWithFormat:@"[[rate 200.0]]Welcome to [[inpt PHON]] CIH1=mUW [[inpt TEXT]] timer version %@.", [CMSCommon applicationVersionString]]];
	[aboutPanel makeKeyAndOrderFront:sender];
	 
	if ([sender tag] == 5000) [aboutPanel orderFrontRegardless];
}

- (IBAction)showHideWindow:(id)sender {
	// This is called by menu items but the actual showing/hiding
	// is handled by Cocoa Bindings (except for the controller window).
}


- (IBAction)showPreferencePanel:(id)sender {
	if (!preferenceController) {
		preferenceController = [[PreferenceController alloc] init];
	}
		
	[preferenceController showWindow:self];
	if ([sender tag] == 5001) [[preferenceController window] orderFrontRegardless];
}



- (IBAction)toggleSpeechRecognition:(id)sender {
	if ([defaults boolForKey:@"enableSpeechRecognition"]) {
		// start speech recognition
		
		SRActive = YES;
		[self initializeSpeechRecognition];		
	} else {
		// stop speech recognition
		SRActive = NO;
		[speechRecognizer stopListening];
	}
}

- (void)stopMusicIfPlaying {
	if (alreadyStoppedMusic) return;
	
	NSAppleEventDescriptor * result = [CMSCommon runCompiledAppleScriptInBundle:@"stopPlayingMusic" matching:@"34ba6a36b9845986ac34624a7d489979"];
	if (nil == result) return;
	musicWasPlaying = [result booleanValue];
	alreadyStoppedMusic = YES;
}

- (void)startMusicIfWasPlaying {
	if (!musicWasPlaying) return;
	
	NSAppleEventDescriptor * result = [CMSCommon runCompiledAppleScriptInBundle:@"startPlayingMusic" matching:@"dcfe488338eabc92b6904f46d9200162"];

	if (nil == result) return;
	
	musicWasPlaying = NO;
	alreadyStoppedMusic = NO;
}

- (void)commonUserStartStopResetActivities {
	if ([defaults boolForKey:@"pauseMusicOnExpire"]) {
		[self startMusicIfWasPlaying];
	}
	
	[speech stopSpeaking];
}

//this will be called when the count down timer expires
- (void)handleCountDownExpired {

	// now we may need to show an alert depending on the user's settings

	if ([defaults boolForKey:@"pauseMusicOnExpire"]) {
		[self stopMusicIfPlaying];	
	}
	
	// how many times should we play the end sound?	
	if ([defaults boolForKey:@"playSoundOnExpire"]) {
		numBeepsLeft = [defaults integerForKey:@"numberAlertBeeps"];
		if (numBeepsLeft > 0) {		
			[self sound:endSound didFinishPlaying:YES];
		}
	}
	
	NSString *alertText = [defaults objectForKey:@"alertText"];

	if ([defaults boolForKey:@"bounceIconOnExpire"]) {
		//bounce the dock icon
		[[NSApplication sharedApplication] requestUserAttention:NSCriticalRequest];
	}
	
	if ([defaults boolForKey:@"speakAlertOnExpire"]) {
		//speak the alert text
		[speech speakStringWhenFree:alertText];
	}
	
	if ([defaults boolForKey:@"launchProgramOnExpire"]) {
		// open the program specified if one exists
		NSString * path = [defaults objectForKey:@"launchProgramPath"];
		
		if ([path length] > 2) {
			NSURL * fileURL = [NSURL fileURLWithPath:path];
			[[NSWorkspace sharedWorkspace] openURL:fileURL];			
		}				
	}
	
	if ([defaults boolForKey:@"displayAlertOnExpire"]) {
		//display an alert box
		int result = NSRunAlertPanel(@"Chimoo Timer Expiration Notice", alertText, @"Okay", nil, nil);
		[speech stopSpeaking];
	}
	
	if (autoRepeatCountdown) {
		[self performSelector:@selector(autoRepeat:) withObject:self afterDelay:autoRepeatDelaySeconds];
	}
	
	if ([defaults boolForKey:@"pauseMusicOnExpire"]) {
		[self startMusicIfWasPlaying];
	}
	
}

- (void)autoRepeat:(id)sender {
	[self reset:sender];
	[self startStop:sender];
}

- (void)updateFromDirectionChange {
	if ([stopWatch isInCountDownMode]) [mainWindow setTitle:@"Chimoo Timer - Count Down"];
	else [mainWindow setTitle:@"Chimoo Timer - Count Up"];
}

- (IBAction)setDirectionUp:(id)sender {
	if ([stopWatch isInCountDownMode]) [self toggleDirection:sender];

	[self willChangeValueForKey:@"countDownMode"];
	countDownMode = [stopWatch isInCountDownMode];
	[self didChangeValueForKey:@"countDownMode"];
}

- (IBAction)setDirectionDown:(id)sender {
	if (![stopWatch isInCountDownMode]) [self toggleDirection:sender];
	
	[self willChangeValueForKey:@"countDownMode"];
	countDownMode = [stopWatch isInCountDownMode];
	[self didChangeValueForKey:@"countDownMode"];	
}

- (IBAction)toggleDirection:(id)sender {
	[stopWatch toggleCountDirection];
	
	[self willChangeValueForKey:@"countDownMode"];
	countDownMode = [stopWatch isInCountDownMode];
	[self didChangeValueForKey:@"countDownMode"];
	
	[sharedTimeFormatter setUseCeil:[stopWatch isInCountDownMode]];
	[internalFormatter setUseCeil:[stopWatch isInCountDownMode]];
	[secTf setUseCeil:[stopWatch isInCountDownMode]];
	
	[self updateFromDirectionChange];
}


// Start/stop the timer.  May be activated by a button or menu item.
- (IBAction)startStop:(id)sender {
	

//	[notifier growl];

		
	// Toggle stopwatch first for accuracy considerations,
	// then recover later if this was the wrong choice.	
	[stopWatch toggleStartStop];

	[self commonUserStartStopResetActivities];
	numBeepsLeft = 0;

	[stopWatch setRefreshRate:[self currentRefreshRate]];
			
	if (![stopWatch isRunning]) { 
		// It was running when this method was called.
		
		if ([defaults boolForKey:@"warnOnStop"]) {
			// Display an alert box before stopping
			int choice = NSRunAlertPanel(@"Stop Timer?",
										 @"Are you sure you want to stop the timer?", @"Stop", @"Cancel", nil);
			
			if (choice != NSAlertDefaultReturn) {
				// Oops! Start the stop watch again.
				[stopWatch resumeTransparentlyFromLastStop];
			}
		}
	}
}


- (void)updateFromStartStop {
	[self willChangeValueForKey:@"startStopText"];
	if ([stopWatch isRunning]) startStopText = @"Stop";
	else startStopText = @"Start";
	[self didChangeValueForKey:@"startStopText"];	
}


/*
//plays a "blank" sound.  Note, this was obtained from the source code
//for KeepSoundAwake 1.3 by Jonathan Feinberg, jdf@pobox.com
- (void)playDummySound {
	char AIFFbytes[54] = {
		'F', 'O', 'R', 'M', 0x00, 0x00, 0x00, 0x2E,
		'A', 'I', 'F', 'F', 'C', 'O', 'M', 'M',
		0x00, 0x00, 0x00, 0x12, 0x00, 0x02, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x10, 0x40, 0x0E, 0xAC, 0x44,
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 'S', 'S', 'N', 'D',
		0x00, 0x00, 0x00, 0x08,
		0x00, 0x00,	0x00, 0x00, 0x00, 0x00, 0x00, 0x00
	};

	NSData *data = [[NSData alloc] initWithBytes:AIFFbytes length:54];
	sound = [[NSSound alloc] initWithData:data];
	[data release];
}
*/


- (IBAction)reset:(id)sender {
		
	// don't worry about it if we send an internal reset message.
	if ((sender != self) && ([defaults boolForKey:@"warnOnStop"])) {
		//display an alert box before resetting
		int choice = NSRunAlertPanel(@"Reset Timer?",
							   @"Are you sure you want to reset the timer?", @"Reset", @"Cancel", nil);

		if (choice != NSAlertDefaultReturn) {
			return;
		}
	}

	numBeepsLeft = 0;
	[self commonUserStartStopResetActivities];
	hasWarned = NO;
	
	[stopWatch reset];
}


- (IBAction)choosePresetStyle:(id)sender {
	id window = [[NSApplication sharedApplication] mainWindow];

	NSFont * font = nil;
	NSColor * color = nil;
	
	if ([[sender title] isEqualToString:@"Traditional"]) {
		font = [NSFont fontWithName:@"Book Antiqua" size:64.0];
		color = [NSColor colorWithDeviceRed:0.0 green:0.0 blue:0.0 alpha:1.0];		
	} else if ([[sender title] isEqualToString:@"Big Blue"]) {
		font = [NSFont fontWithName:@"Bernard MT Condensed" size:288.0];
		color = [NSColor colorWithDeviceRed:0.21 green:0.37 blue:1.0 alpha:1.0];
	} else if ([[sender title] isEqualToString:@"Ornaments"]) {
		font = [NSFont fontWithName:@"Bodoni Ornaments ITC TT" size:144.0];
		color = [NSColor colorWithDeviceRed:0.0 green:0.0 blue:0.0 alpha:1.0];
	} else if ([[sender title] isEqualToString:@"Fun"]) {
		font = [NSFont fontWithName:@"Curlz MT" size:64.0];
		color = [NSColor colorWithDeviceRed:0.0 green:0.0 blue:0.0 alpha:1.0];
	} else {
		if (window == mainWindow) [self restoreMainWindowFontAndColor];
		if (window == multiplierWindow) [self restoreMultiplierWindowFontAndColor];
		return; // it takes care of setting them.
	}
	
	[self resizeWindow:window withNewFont:font];
	if (window == mainWindow) [readout setTextColor:color];
	else [multiplierField setTextColor:color];

}

- (void)changeColor:(id)sender {
	id window = [[NSApplication sharedApplication] mainWindow];
	if (!((window == mainWindow) || (window == multiplierWindow))) return;
	
	id field = nil;
	if (window == mainWindow) field = readout;
	else if (window == multiplierWindow) field = multiplierField;
	
	[field setTextColor:[sender color]];
}


- (NSFont*)fontForWindow:(NSWindow*)window {
	if (!((window == mainWindow) || (window == multiplierWindow))) return nil;
	
	id field = nil;
	if (window == mainWindow) field = readout;
	else if (window == multiplierWindow) field = multiplierField;

	NSFont *font = [field font];
	return font;
}


- (void)changeFont:(id)sender {
	id window = [[NSApplication sharedApplication] mainWindow];

	NSFont *oldFont = [self fontForWindow:window];	
	if (nil == oldFont) return;
	
    NSFont *newFont = [sender convertFont:oldFont];

	[self resizeWindow:window withNewFont:newFont];
}

- (void)resizeWindow:(id)window withNewFont:(NSFont*)font {
	if (!((window == mainWindow) || (window == multiplierWindow))) return;
	
	id field = nil;
	if (window == mainWindow) field = readout;
	else if (window == multiplierWindow) field = multiplierField;

	const int OFFSET = 46; // from top of text field to top of window, and bottom of field to bottom + a little
	
	NSRect windowFrame = [window frame];
	NSSize windowSize = windowFrame.size;
	
	// this includes all possible characters, so it may be larger than necessary.
	//NSRect boundRect = [font boundingRectForFont];
	
	NSDictionary * attrs = [NSDictionary dictionaryWithObject:font forKey:NSFontAttributeName];																
	
	NSString * string = [NSString stringWithString:@"0123456789:.,hoursminutesseconds"];
	NSSize boundSize = [string sizeWithAttributes:attrs];
	
	float height = boundSize.height;
	NSRect cRect = [window contentRectForFrameRect:[window frame]];
	cRect.size.height = (int)ceilf(height) + OFFSET;
		
	string = [field stringValue];
	NSSize currentSize = [string sizeWithAttributes:attrs];
	
	// make wider only if necessary
	if (currentSize.width > NSWidth([field frame])) {
		cRect.size.width = (int)ceilf(currentSize.width) + 100;
	}
	
	[window setContentSize:cRect.size];
		
/*	
	int newFieldHeight = [font defaultLineHeightForFont] + ([font defaultLineHeightForFont] - ([font ascender] + [font descender]));
	
	int diff = windowSize.height - [field frame].size.height;
	NSRect newWindowFrame = windowFrame;
	newWindowFrame.size.height = newFieldHeight + diff + OFFSET;
	if (!internalFontUpdate) newWindowFrame.origin.y = windowFrame.origin.y + (windowSize.height - newWindowFrame.size.height);

	if (newFieldHeight != [field frame].size.height) {
		[window setFrame:newWindowFrame display:YES animate:YES];
	}
*/	
		
	[field setFont:font];
}


// Used when the user clicks the zoom button.
//
// Only zoom the width.
//
- (NSRect)windowWillUseStandardFrame:(NSWindow *)sender defaultFrame:(NSRect)defaultFrame {
	if (!((sender == mainWindow) || (sender == multiplierWindow))) return [sender frame];
	
	// must be multiplier or main window
	
	id field = nil;
	if (sender == mainWindow) field = readout;
	else if (sender == multiplierWindow) field = multiplierField;
			
	NSDictionary * attrs = [NSDictionary dictionaryWithObject:[self fontForWindow:sender] forKey:NSFontAttributeName];																

	NSString * string = [field stringValue];
	NSSize boundSize = [string sizeWithAttributes:attrs];
		
	NSRect idealRect = [sender frame];
	
	idealRect.size.width = (int)ceilf(boundSize.width) + 100;
//	if ([sender minSize].width < idealRect.size.width) {
//		idealRect.size.width = [sender minSize].width;
//	}
	
	return idealRect;
}


- (float)currentRefreshRate {
	if (![defaults boolForKey:@"showSecondFractions"]) {
		// Not showing fractions, so no need to go fast; the user will never notice.
		return [defaults floatForKey:@"backgroundRefreshRate"];
	} else {
		// Showing second fractions, so fast if in foreground.
		if ([defaults boolForKey:@"dockIconIsCurrentlyHidden"]) {
			return [defaults floatForKey:@"foregroundRefreshRate"];
		} else {
			if (enableMenu && (!(enableMini || enableMain || enableMulti))) return [defaults floatForKey:@"foregroundRefreshRate"];
			if (inForeGround) return [defaults floatForKey:@"foregroundRefreshRate"];
			else return [defaults floatForKey:@"backgroundRefreshRate"];
		}
	}
	
	return [defaults floatForKey:@"foregroundRefreshRate"];
}


- (void)updateTextReadouts:(StopWatch*)theWatch {
	NSNumber * n = [stopWatch elapsedTimeInSecondsAsNumber];
	double t = [n doubleValue];
	
	if (enableMain) {
		[readout setObjectValue:n];
	
		if ([theWatch isInCountDownMode]) {
			double until = [theWatch countingDownUntilInSeconds];
			[mainWindowProgressBar setMaxValue:until];
			[mainWindowProgressBar setDoubleValue:until - t];
		}
	}
	
	if (enableMenu) [statusItem setTitle:[sharedTimeFormatter stringForObjectValue:n]];	
	if (enableMulti) [multiplierField setFloatValue:multiplierFactor * t];
	if (enableMini) [miniWindowController updateDisplayWithSeconds:t];
}

- (void)stopWatchDidStart:(StopWatch*)theWatch {	
	[self updateTextReadouts:theWatch];
	[mainWindowProgressBar startAnimation:self];

	[self updateFromStartStop];
	[self initializeSpeechRecognition];	
}

- (void)stopWatchDidStop:(StopWatch*)theWatch {	
	[self updateTextReadouts:theWatch];
	[mainWindowProgressBar stopAnimation:self];
	[self updateFromStartStop];
	[self initializeSpeechRecognition];
}

- (void)stopWatchDidExpire:(StopWatch*)theWatch {
	[self handleCountDownExpired];
}

- (void)stopWatchDidTickSecond:(StopWatch*)theWatch {
	[self updateTextReadouts:theWatch];
	
	NSNumber * time = [theWatch elapsedTimeInSecondsAsNumber];
	
	if ([defaults boolForKey:@"enableSecondTickSound"]) {
		[secondTickSound play];
	}
	
	if ((speakTimeOnInterval) && (![speech isSpeaking])) {
		int i = [time intValue];
	
		if (fabs(lastSpokenTimeInterval - i) >= (float)intervalToSpeakSeconds) {
			[self elapsed:self];
			lastSpokenTimeInterval = i;
		}		
	}
	
	if (countDownMode && [defaults boolForKey:@"speakLastTen"] && ([time floatValue] > 0.0) && ([time floatValue] < 10.0)) {
		if (!alreadyStoppedMusic) [self stopMusicIfPlaying];
		if ([stopWatch isRunning]) [speech stopSpeakingAndSpeakString:[secTf stringForObjectValue:time]];
	} else if (([defaults boolForKey:@"speakSeconds"]) && (![speech isSpeaking])) {
		[speech speakStringIfFree:[secTf stringForObjectValue:time]];
	}
	
	if (warnBeforeExpire && countDownMode) {
		if (!hasWarned && (secDown >= warnBeforeExpireSeconds)) {
			if ([time intValue] <= warnBeforeExpireSeconds) {

				// warn of impending expiration
				CMSTimeFormatter * form = [[CMSTimeFormatter alloc] init];
				[form setUseSpeakableFormat:YES];
				[form setShowSecondFractions:NO];
			
				time = [theWatch elapsedTimeInSecondsAsNumber];
				[speech stopSpeakingAndSpeakString:[NSString stringWithFormat:@"Warning! Your timer will expire in %@.", [form stringForObjectValue:time]]];				
				[form release];
					
				hasWarned = YES;
			}
		}
	}
}

- (void)stopWatchDidTickMinute:(StopWatch*)theWatch {	
	[self updateTextReadouts:theWatch];
	
	if ([defaults boolForKey:@"enableMinuteTickSound"]) {
		[minuteTickSound play];
	}
}

- (void)stopWatchDidTickHour:(StopWatch*)theWatch {	
	[self updateTextReadouts:theWatch];
	
	if ([defaults boolForKey:@"enableHourTickSound"]) {
		[hourTickSound play];
	}
}


- (void)stopWatchDidReset:(StopWatch*)theWatch {
	[self updateTextReadouts:theWatch];
}


// This method is called every time the display should update (set by refresh rate),
// so it should be optimized if possible.
- (void)stopWatchDidUpdate:(StopWatch*)theWatch {
	
	// Only need to redraw if we're showing fractions of seconds - otherwise the 
	// stopWatchDidTickSecond method will cause updates every second.
	
	if ([defaults boolForKey:@"showSecondFractions"]) [self updateTextReadouts:theWatch];
}

- (IBAction)toggleMenuExtra:(id)sender {
	
}

- (IBAction)hideMenuExtra:(id)sender {
	if ([defaults boolForKey:@"showMenuExtra"]) [defaults setBool:NO forKey:@"showMenuExtra"];
	
	[statusBar removeStatusItem:statusItem];
	[statusItem release]; statusItem = nil;
}

- (void)enableMenuExtraIfNecessary {
	if (![defaults boolForKey:@"showMenuExtra"]) {
		[self hideMenuExtra:self];
		return;
	}
	
	// If we make it to here, that means show it.
	if (nil != statusItem) {
		// is it already showing?
		[self updateTextReadouts:stopWatch];
		return;
	}
		
	if (nil == statusBar) statusBar = [[NSStatusBar systemStatusBar] retain];	
	if (nil == statusItem) statusItem = [[statusBar statusItemWithLength:NSVariableStatusItemLength] retain];
	[statusItem setTitle:@""];
	[statusItem setHighlightMode:YES];
	[statusItem setMenu:menuExtra];	
	[self updateTextReadouts:stopWatch];
}




- (IBAction)sendFeedback:(id)sender {
	NSString * body = [NSString stringWithFormat:@"Chimoo Timer Version %@\nMac OS X %@", [CMSCommon applicationVersionString], [CMSCommon operatingSystemVersionString]];
	[CMSCommon composeEmailTo:@"support@chimoosoft.com"
				  withSubject:[NSString stringWithFormat:@"Chimoo Timer %@ Support", [CMSCommon applicationVersionString]]
					  andBody:body];	
}

- (IBAction)openURLForTitle:(id)sender {
	if (sender == mailToButton) {
		[self sendFeedback:self];
	} else {
		[CMSCommon openURLFromString:[sender alternateTitle]];
	}
}

- (IBAction)openChimoosoftHome:(id)sender {
	[CMSCommon openChimoosoftHomePage];
}

- (IBAction)alreadyDonated:(id)sender {
	
}

- (IBAction)donateWebPage:(id)sender {
	[CMSCommon openDonationPage];
}


- (void)updateSounds {
	[endSound release];
	endSound = [[NSSound soundNamed: [defaults objectForKey:@"alertSoundName"]] retain];
	[endSound setDelegate:self];
	
	[secondTickSound release];
	secondTickSound = [[NSSound soundNamed:[defaults objectForKey:@"secondTickSoundName"]] retain];
	
	[minuteTickSound release];
	minuteTickSound = [[NSSound soundNamed:[defaults objectForKey:@"minuteTickSoundName"]] retain];
	
	[hourTickSound release];
	hourTickSound = [[NSSound soundNamed:[defaults objectForKey:@"hourTickSoundName"]] retain];	
}

- (int)factorFromHMSPopupMenu:(NSString*)string {
	if ([string isEqualToString:@"hours"]) return 3600;
	if ([string isEqualToString:@"minutes"]) return 60;
	if ([string isEqualToString:@"seconds"]) return 1;
	return 1;
}

- (void)userDefaultsChanged:(NSNotification *)aNotification {
	
	enableMain = [defaults boolForKey:@"showMainWindow"];
	enableMenu = [defaults boolForKey:@"showMenuExtra"];
	enableMini = [defaults boolForKey:@"showMiniWindow"];
	enableMulti = [defaults boolForKey:@"showMultiplierWindow"];
	
	autoRepeatCountdown = [defaults boolForKey:@"autoRepeatCountdown"];
	autoRepeatDelaySeconds = [self factorFromHMSPopupMenu:[defaults stringForKey:@"autoRepeatDelayUnit"]] * [defaults floatForKey:@"autoRepeatDelay"];
	
	warnBeforeExpire = [defaults boolForKey:@"warnBeforeExpire"];
	warnBeforeExpireSeconds = (int)([self factorFromHMSPopupMenu:[defaults stringForKey:@"warnBeforeExpireUnit"]] * [defaults floatForKey:@"warnBeforeExpireTime"]);
	
	speakTimeOnInterval = [defaults boolForKey:@"speakTimeOnInterval"];
	intervalToSpeakSeconds = [self factorFromHMSPopupMenu:[defaults stringForKey:@"speakTimeIntervalUnit"]] * [defaults integerForKey:@"speakTimeInterval"];
		
	multiplierFactor = [defaults floatForKey:@"multiplierFactor"];
	
	NSString * format = [defaults stringForKey:@"mainFormat"];
	if ([format isEqualToString:@"HH:MM:SS"]) [sharedTimeFormatter setUseHMSFormat:YES];
	else if ([format isEqualToString:@"Natural Language"]) [sharedTimeFormatter setUseSpeakableFormat:YES];
	else if ([format isEqualToString:@"Seconds Only"]) [sharedTimeFormatter setUseFullSeconds:YES];
	[sharedTimeFormatter setShowSecondFractions:[defaults boolForKey:@"showSecondFractions"]];
	
	if (![defaults boolForKey:@"dockIconIsCurrentlyHidden"]) {	
		// dock icon is visible
		
		if ([defaults boolForKey:@"showMainWindow"]) {
			if (![mainWindow isVisible]) [mainWindow makeKeyAndOrderFront:self];
			[self updateFromDirectionChange];
		} else [mainWindow orderOut:self];
		
		if ([defaults boolForKey:@"showMultiplierWindow"]) {
			if (![multiplierWindow isVisible]) [multiplierWindow makeKeyAndOrderFront:self];
		} else [multiplierWindow orderOut:self];
		
		if ([defaults boolForKey:@"showMiniWindow"]) {
			if (![miniWindow isVisible]) [miniWindow makeKeyAndOrderFront:self];
		} else [miniWindow orderOut:self];
	} else {
		// dock icon is hidden
		enableMini = enableMain = enableMulti = NO;
	}
	
	[self enableMenuExtraIfNecessary];	
	
	[stopWatch setRefreshRate:[self currentRefreshRate]];
	
	[self toggleSpeechRecognition:self];
	[speech setRate:[defaults floatForKey:@"speechRate"]];
	[speech setVoiceWithIdentifier:[defaults objectForKey:@"defaultVoiceIdentifier"]];
	
}


- (void)setupMenus {
	//set up menus (move this somewhere else later?)
	
	NSComboBox *box;
	int i;
	
	box = hMenu;
	for (i = 0; i <= 24; i++) {		
		[box addItemWithObjectValue:[NSNumber numberWithInt:i]];
	}
	[box setNeedsDisplay];  //to redraw it
	
	box = mMenu;
	for (i = 0; i < 60; i++) {
		[box addItemWithObjectValue:[NSNumber numberWithInt:i]];
	}
	[box setNeedsDisplay];  //to redraw it
	
	box = sMenu;
	for (i = 0; i < 60; i++) {
		[box addItemWithObjectValue:[NSNumber numberWithInt:i]];
	}
	[box setNeedsDisplay];  //to redraw it
	
}




// Delegate methods for CMSVersioning.

- (NSURL*)productURL {
	return [NSURL URLWithString:@"http://www.chimoosoft.com/products/chimootimer/"];
}


@end


#pragma mark -
#pragma mark Delegate Methods


// For the delegate, this is called when the application is finished launching...
@implementation Controller(NSApplicationNotifications)

- (void)sound:(NSSound *)sound didFinishPlaying:(BOOL)aBool {
	if (numBeepsLeft > 0) {
		[endSound performSelector:@selector(play) withObject:nil afterDelay:0.25];
		numBeepsLeft--;
	}		
}



- (void)comboBoxSelectionDidChange:(NSNotification *)notification {
	int tag;
	tag = [[notification object] tag];
	//NSLog(@"comboBoxSelectionDidChange tag=%d", tag);
}

- (void)applicationWillFinishLaunching:(NSNotification *)aNotification {
	
}

// this is where we will init stuff
- (void)applicationDidFinishLaunching:(NSNotification*)notification {		
		
	
//	if (![defaults boolForKey:@"dockIconIsCurrentlyHidden"]) [CMSCommon runPreReleaseWarning];
	
	if ([defaults boolForKey:@"enableSpeechRecognition"]) {
		[self initializeSpeechRecognition];
		SRActive = YES;
	}		
	
	if ([defaults boolForKey:@"showDisclaimer"]) {
		//[speech speakStringIfFree:@"[[rate 210.0]][[inpt PHON]] CIH1=mUW [[inpt TEXT]] Timer has been designed to be highly accurate, however it may not be suitable for use in situations where precise time intervals need to be measured.  You may turn this alert off in the [[inpt PHON]] CIH1=mUW [[inpt TEXT]] Timer preferences."];
		int clicked = NSRunAlertPanel(@"Accuracy Disclaimer",
									  @"Chimoo Timer has been designed to be highly accurate, however it may not be suitable for use in situations where precise time intervals need to be measured.  \n\nYou may turn this alert off in the Advanced tab of the Chimoo Timer preferences.",
									  @"OK", nil, nil);					
	}
	
	if (![defaults boolForKey:@"dockIconIsCurrentlyHidden"]) {	
		[self setupMenus];
	}
	
	[self updateStopWatchCountDownTime];			
	
	[self reset:self];

	
	[CMSDonations setupWithDollarCost:@"$10"];	
}





- (void)applicationWillTerminate:(NSNotification *)aNotification {
	[defaults setObject:[NSArchiver archivedDataWithRootObject:[readout font]] forKey:@"mainWindowFont"];
	[defaults setObject:[NSArchiver archivedDataWithRootObject:[multiplierField font]] forKey:@"multiplierWindowFont"];
	[defaults setObject:[NSArchiver archivedDataWithRootObject:[readout textColor]] forKey:@"mainWindowTextColor"];
	[defaults setObject:[NSArchiver archivedDataWithRootObject:[multiplierField textColor]] forKey:@"multiplierWindowTextColor"];
	
	[defaults setBool:[stopWatch isInCountDownMode] forKey:@"countDownAtLastLaunch"];
}

- (void)applicationDidBecomeActive:(NSNotification *)aNotification {
	inForeGround = YES;
	[stopWatch setRefreshRate: [self currentRefreshRate]];
}


- (void)applicationDidResignActive:(NSNotification *)aNotification {
	inForeGround = NO;
	
	[stopWatch setRefreshRate: [self currentRefreshRate]];
	
}

- (void)windowDidBecomeKey:(NSNotification *)aNotification {
	id window = [aNotification object];	
	NSFont * font = [self fontForWindow:window];
	if (nil == font) return;
	
	// makes sure the font panel has the appropriate font selected
	[[NSFontManager sharedFontManager] setSelectedFont:font isMultiple:NO];
}

- (void)windowDidMove:(NSNotification *)aNotification {
	//NSLog(@"%f",[[aNotification object] frame].origin.y);
}

- (NSSize)windowWillResize:(NSWindow *)sender toSize:(NSSize)proposedFrameSize {
	if ((sender == mainWindow) || (sender == multiplierWindow)) {
		NSSize newSize = proposedFrameSize;
		newSize.height = [sender frame].size.height;
		
		return newSize;
	}
	
	return proposedFrameSize;
}


- (BOOL)windowShouldClose:(id)sender {
	// miniWindow taken care of in miniWindowController
	if (sender == mainWindow) [defaults setBool:NO forKey:@"showMainWindow"];
	if (sender == multiplierWindow) [defaults setBool:NO forKey:@"showMultiplierWindow"];
	
	return YES;
}

- (void)windowWillClose:(NSNotification *)aNotification {
}

- (void)windowDidMiniaturize:(NSNotification *)aNotification {
}


- (void)windowDidDeminiaturize:(NSNotification *)aNotification {
}


- (void)windowDidExpose:(NSNotification *)aNotification {

}




@end
